﻿/* 执行失败的处理
*   1. 子站不接受自动同步，则发送通知
*   2. 子站接受自动同步，则在同步完成后发送通知
*   3. 发生异常则记录日志, 并发送同步失败的通知
*/

using Abp.Configuration;
using Abp.Dependency;
using Abp.Domain.Entities;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Abp.Localization;
using Abp.MultiTenancy;
using Abp.Notifications;
using Abp.Threading;
using Mt.Authorization.Users;
using Mt.Site.Core.Base.Localization;
using Mt.Site.Core.Configuration;
using Mt.Site.Core.ContentManagement.Entities;
using Mt.Site.Core.Navigations;
using Mt.Site.Core.Navigations.Entities;
using Mt.Site.Core.Navigations.Permissions;
using Mt.Site.Core.Navigations.Route;
using Mt.Site.Core.Notifications;
using Mt.Site.Core.SEO;
using Mt.Site.Core.Storage;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using System.Transactions;

namespace Mt.Site.Core.Synchronization
{
    public class SynchronizationHelper : MtSiteDomainServiceBase, ISynchronizationHelper
    {
        private readonly INotificationPublisher _notificationPublisher;
        private readonly UserManager2 _userManager2;
        private readonly IRepository<MtApplicationLanguage> _applicationLanguageRepository;
        private readonly IRepository<Menu, Guid> _menuRepository;

        private readonly IIocResolver _iocResolver;
        private readonly IMenuCache _menuCache;
        private readonly IFindEntityTypeHelper _findEntityTypeHelper;

        private readonly ITenantStore _tenantStore;

        public SynchronizationHelper(
            INotificationPublisher notificationPublisher,
            UserManager2 userManager2,
            IRepository<MtApplicationLanguage> applicationLanguageRepository,
            IRepository<Menu, Guid> menuRepository,
            IIocResolver iocResolver,
            IMenuCache menuCache,
            IFindEntityTypeHelper findEntityTypeHelper,
            ITenantStore tenantStore)
        {
            _notificationPublisher = notificationPublisher;
            _userManager2 = userManager2;
            _applicationLanguageRepository = applicationLanguageRepository;
            _menuRepository = menuRepository;
            _iocResolver = iocResolver;
            _menuCache = menuCache;
            _findEntityTypeHelper = findEntityTypeHelper;
            _tenantStore = tenantStore;
        }

        #region Synchronization

        [UnitOfWork]
        public IReadOnlyList<SonSiteInfo> GetSonSite(int fromTenantId, string fromLanguage, long menuId,bool isForMenuItem = false)
        {
            var result = new List<SonSiteInfo>(); 
            using (CurrentUnitOfWork.DisableFilter(
                    AbpDataFilters.MayHaveTenant,
                    AbpDataFilters.MustHaveTenant))
            {
                var fromSiteLanguageEntity = _applicationLanguageRepository.FirstOrDefault(
                    u => u.TenantId == fromTenantId && u.Name == fromLanguage && u.IsDisabled == false && u.IsDeleted == false);

                if(fromSiteLanguageEntity != null)
                {
                    var sonSiteLanguageEntities = _applicationLanguageRepository.GetAllList(
                        u=> u.SourceLanguageId == fromSiteLanguageEntity.Id && u.IsDisabled == false && u.TenantId != null && u.IsDeleted == false);

                    foreach (var item in sonSiteLanguageEntities)
                    {
                        var infoData = new SonSiteInfo { TargetTenantId = item.TenantId.Value, TargetLanguage = item.Name };
                        if (isForMenuItem)
                        {
                            infoData.AutoSynchronous = ShouldAutoSynchronizeForMenuItem(infoData.TargetTenantId, menuId);
                        }
                        else
                        {
                            infoData.AutoSynchronous = ShouldAutoSynchronizeForContent(infoData.TargetTenantId, menuId);
                        }
                        result.Add(infoData);
                    }
                }
            }

            return result;
        }


        [UnitOfWork]
        public MotherSiteInfo GetMotherSiteInfo(int currentTenantId, string currentLanguage)
        {
            using (CurrentUnitOfWork.DisableFilter(
                    AbpDataFilters.MayHaveTenant,
                    AbpDataFilters.MustHaveTenant))
            {
                var fromSiteLanguageEntity = _applicationLanguageRepository.FirstOrDefault(
                    u => u.TenantId == currentTenantId && u.Name == currentLanguage && u.IsDisabled == false && u.IsDeleted == false);

                if (fromSiteLanguageEntity != null)
                {
                    var motherEntity = _applicationLanguageRepository.FirstOrDefault(
                            u => u.Id == fromSiteLanguageEntity.SourceLanguageId && u.IsDisabled == false && u.TenantId != null && u.IsDeleted == false);

                    if (motherEntity != null)
                    {
                        return new MotherSiteInfo
                        {
                            MotherLanguage = motherEntity.Name,
                            MotherTenantId = motherEntity.TenantId.Value,
                            TenantDisplayName = _tenantStore.Find(motherEntity.TenantId.Value).TenancyName
                        };
                    }
                }
            }
            return null;
        }

        public bool ShouldSynchronize(Entity<Guid> entity)
        {
            if (entity is IAutoSynchronous)
            {
                return (entity as IAutoSynchronous).AutoSynchronous;
            }

            return false;
        }

        public bool ShouldSynchronize(int tenantId)
        {
            return SettingManager.GetSettingValueForTenant<bool>(AppSettings.Synchronous.DataSynchronous, tenantId);
        }

        private bool ShouldAutoSynchronizeForContent(int tenantId, long menuId)
        {
            if (ShouldAutoSynchronize(tenantId))
            {
                var toMenu = _menuRepository.FirstOrDefault(
                    m => m.AuxiliaryId == menuId && m.TenantId == tenantId && m.IsDeleted == false);
                if (toMenu != null)
                {
                    return toMenu.AutoSynchronousForContent;
                }
            }
            return false;
        }

        private bool ShouldAutoSynchronizeForMenuItem(int tenantId, long menuId)
        {
            if (ShouldAutoSynchronize(tenantId))
            {
                var toMenu = _menuRepository.FirstOrDefault(
                    m => m.AuxiliaryId == menuId && m.TenantId == tenantId);
                if (toMenu != null)
                {
                    return toMenu.AutoSynchronous;
                }
            }
            return false;
        }

        private bool ShouldAutoSynchronize(int tenantId)
        {
            return ShouldSynchronize(tenantId)
                && SettingManager.GetSettingValueForTenant<bool>(AppSettings.Synchronous.AutoSynchronous, tenantId);
        }

        #endregion

        #region FindEntityType

        public Entity<Guid> FindContentTypeByContentGuid(Guid contentId, int tenantId)
        {
            return _findEntityTypeHelper.FindContentTypeByGuid(
                contentId, tenantId,
                new List<Type> {
                    typeof(Content),
                    typeof(Menu)
                });
        }

        public Entity<Guid> FindContentTypeByContentId(long contentId, int tenantId, string language)
        {
            return _findEntityTypeHelper.FindContentTypeByLongId(
                contentId, tenantId, language,
                new List<Type>
                {
                    //typeof(Solution),
                    //typeof(Info),
                    //typeof(News),
                    //typeof(Contact),
                    //typeof(Download)
                });
        }


        #endregion

        #region Notice

        public async Task SendNoticeForFailAsync(NoticeArgs args)
        {
            using (var uow = UnitOfWorkManager.Begin(new UnitOfWorkOptions
            {
                Scope = TransactionScopeOption.RequiresNew
            }))
            {
                using (CurrentUnitOfWork.EnableFilter(
                    AbpDataFilters.MayHaveTenant,
                    AbpDataFilters.MustHaveTenant,
                    MtSiteConsts.AbpDataFilters.MustHaveLanguage,
                    AbpDataFilters.SoftDelete))
                {
                    var user = _userManager2.FindNormalUsersByPermissions(args.ToTenantId, args.MenuId, new List<string> { MenuPermissions.Pages_Tenant_Menu_Content });
                    if (user.Count > 0)
                    {
                        var data = new LocalizableMessageNotificationData(new LocalizableString("SendNoticeForFail", MtSiteConsts.LocalizationSourceName));
                        data["menuId"] = args.MenuId;
                        data["menuName"] = GetMenuName(args.MenuId, args.ToTenantId, args.ToLanguage);
                        data["title"] = args.Title;
                        data["fromId"] = args.FromId;
                        data["toId"] = args.ToId;
                        data["type"] = args.Type.ToString();

                        await _notificationPublisher.PublishAsync(
                           AppNotificationNames.SendNoticeForFail,
                           data,
                           severity: NotificationSeverity.Error,
                           userIds: user.ToArray()
                           );
                    }
                }

                uow.Complete();
            }
        }

        [UnitOfWork]
        public async Task SendNoticeForSuccessAsync(NoticeArgs args)
        {
            using (var uow = UnitOfWorkManager.Begin(new UnitOfWorkOptions
            {
                Scope = TransactionScopeOption.RequiresNew
            }))
            {
                using (CurrentUnitOfWork.EnableFilter(
                    AbpDataFilters.MayHaveTenant,
                    AbpDataFilters.MustHaveTenant,
                    MtSiteConsts.AbpDataFilters.MustHaveLanguage,
                    AbpDataFilters.SoftDelete))
                {
                    var user = _userManager2.FindNormalUsersByPermissions(args.ToTenantId, args.MenuId, new List<string> { MenuPermissions.Pages_Tenant_Menu_Content });
                    if (user.Count > 0)
                    {
                        var data = new LocalizableMessageNotificationData(new LocalizableString("SendNoticeForSuccess", MtSiteConsts.LocalizationSourceName));
                        data["menuId"] = args.MenuId;
                        data["menuName"] = GetMenuName(args.MenuId, args.ToTenantId, args.ToLanguage);
                        data["title"] = args.Title;
                        data["fromId"] = args.FromId;
                        data["toId"] = args.ToId;
                        data["type"] = args.Type.ToString();

                        await _notificationPublisher.PublishAsync(
                           AppNotificationNames.SendNoticeForSuccess,
                           data,
                           severity: NotificationSeverity.Success,
                           userIds: user.ToArray()
                           );
                    }
                }

                uow.Complete();
            }
        }

        public async Task SendNoticeForFailAsync(NoticeArgs args, long? menuId, List<string> permissions)
        {
            using (var uow = UnitOfWorkManager.Begin(new UnitOfWorkOptions
            {
                Scope = TransactionScopeOption.RequiresNew
            }))
            {
                using (CurrentUnitOfWork.EnableFilter(
                    AbpDataFilters.MayHaveTenant,
                    AbpDataFilters.MustHaveTenant,
                    MtSiteConsts.AbpDataFilters.MustHaveLanguage,
                    AbpDataFilters.SoftDelete))
                {
                    var user = _userManager2.FindNormalUsersByPermissions(args.ToTenantId, menuId, permissions);
                    if (user.Count > 0)
                    {
                        var data = new LocalizableMessageNotificationData(new LocalizableString("SendNoticeForSuccess", MtSiteConsts.LocalizationSourceName));
                        data["menuId"] = args.MenuId;
                        data["menuName"] = GetMenuName(args.MenuId, args.ToTenantId, args.ToLanguage);
                        data["title"] = args.Title;
                        data["fromId"] = args.FromId;
                        data["toId"] = args.ToId;
                        data["type"] = args.Type.ToString();

                        await _notificationPublisher.PublishAsync(
                           AppNotificationNames.SendNoticeForSuccess,
                           data,
                           severity: NotificationSeverity.Success,
                           userIds: user.ToArray()
                           );
                    }
                }

                uow.Complete();
            }
        }
        public async Task SendNoticeForSuccessAsync(NoticeArgs args, long? menuId, List<string> permissions)
        {
            using (var uow = UnitOfWorkManager.Begin(new UnitOfWorkOptions
            {
                Scope = TransactionScopeOption.RequiresNew
            }))
            {
                using (CurrentUnitOfWork.EnableFilter(
                    AbpDataFilters.MayHaveTenant,
                    AbpDataFilters.MustHaveTenant,
                    MtSiteConsts.AbpDataFilters.MustHaveLanguage,
                    AbpDataFilters.SoftDelete))
                {
                    var user = _userManager2.FindNormalUsersByPermissions(args.ToTenantId, menuId, permissions);
                    if (user.Count > 0)
                    {
                        var data = new LocalizableMessageNotificationData(new LocalizableString("SendNoticeForSuccess", MtSiteConsts.LocalizationSourceName));
                        data["menuId"] = args.MenuId;
                        data["menuName"] = GetMenuName(args.MenuId, args.ToTenantId, args.ToLanguage);
                        data["title"] = args.Title;
                        data["fromId"] = args.FromId;
                        data["toId"] = args.ToId;
                        data["type"] = args.Type.ToString();

                        await _notificationPublisher.PublishAsync(
                           AppNotificationNames.SendNoticeForSuccess,
                           data,
                           severity: NotificationSeverity.Success,
                           userIds: user.ToArray()
                           );
                    }
                }

                uow.Complete();
            }
        }
        #endregion


        public void SyncRouteStr(Guid fromId, Guid toId, int toTenantId, string LanguageName)
        {
            /**
                1. 当调用此方法同步路由时，主数据应该已同步完成，所以两个id都会存在
            */
            using (var scope = _iocResolver.CreateScope())
            {
                var routeStore = scope.Resolve<IRouteStore>();
                routeStore.CopyARouteStr(fromId, toId, toTenantId, LanguageName);
            }
        }
        public void SyncRouteStr(Guid fromId, string fromLanguageName, Guid toId, int toTenantId, string LanguageName)
        {
            using (var scope = _iocResolver.CreateScope())
            {
                var routeStore = scope.Resolve<IRouteStore>();
                routeStore.CopyARouteStr(fromId, fromLanguageName, toId, toTenantId, LanguageName);
            }
        }

        public async Task DeleteSyncRouteAsync(Guid relatedId, int tenantId)
        {
            using (var scope = _iocResolver.CreateScope())
            {
                var routeStore = scope.Resolve<IRouteStore>();
                await routeStore.DeleteAsync(relatedId, tenantId);
            }
        }

        public void DeleteSeo(Guid relateId)
        {
            using (var scope = _iocResolver.CreateScope())
            {
                var seoRepository = scope.Resolve<IRepository<Seo, Guid>>();
                seoRepository.Delete(item => item.RelatedId == relateId);
            }
        }

        public Guid GetMenuId(long auxiliaryId, int tenantId)
        {
            var menuEntity = _menuRepository.FirstOrDefault(
                item => item.TenantId == tenantId && item.AuxiliaryId == auxiliaryId);
            if (menuEntity != null)
            {
                return menuEntity.Id;
            }

            return Guid.Empty;
        }

        public long GetAuxiliaryIdForMenuItem(Guid Id)
        {
            var menuEntity = _menuRepository.FirstOrDefault(Id);
            if(menuEntity != null)
            {
                return menuEntity.AuxiliaryId;
            }

            return 0;
        }

        #region Private

        private string GetMenuName(long menuId, int tenantId, string language)
        {
            try
            {
                var item = AsyncHelper.RunSync(() => _menuCache.GetMenuIdTraceCacheItemAsync(tenantId, menuId));
                var culture = CultureInfo.GetCultureInfo(language);
                return item.GetDisplayName(culture);
            }
            catch 
            {
                var menuEntity = _menuRepository.FirstOrDefault(
                        item => item.TenantId == tenantId && item.AuxiliaryId == menuId);
                if (menuEntity != null)
                {
                    return menuEntity.DisplayName;
                }
            }
            return "";
        }
        #endregion
    }
}
